<?php

/*********************************************
 * Script:	GrandMatrix Object (modified from "Multi-Dimentional Array Handler (marray) Object")
 * Authors: Colin Jermain; modified by Jesdisciple
 * Contact: http://phpknowhow.com/contact.php
 * Date:		June 7th, 2009
 * Version: 1.0
 * License: 
 *
 *		Copyright (C) 2008 Colin Jermain; (C) 2009 Jesdisciple
 *
 *		This program is free software: you can redistribute it and/or modify
 *		it under the terms of the GNU General Public License as published by
 *		the Free Software Foundation, either version 3 of the License, or
 *		(at your option) any later version.
 *
 *		This program is distributed in the hope that it will be useful,
 *		but WITHOUT ANY WARRANTY; without even the implied warranty of
 *		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	See the
 *		GNU General Public License for more details.
 *
 *		You should have received a copy of the GNU General Public License
 *		along with this program.	If not, see <http://www.gnu.org/licenses/>.
 *
 * Notes:
 *	(1) Read documentation before using.
 *	(2) Exercise caution because of the use of eval().
 *
*********************************************/

/*
 * Given a raw regex in a string (and, optionally, a pattern-modifiers string), return a string suitable
 * for use as a preg pattern. The regex is wrapped in delimiters, with the modifiers (if any) appended.
 */
function preg_regex_to_pattern($raw_regex, $modifiers = "", $delimiter = '/'){// modified from http://tool.87871.com/help/Mastering%20Regular%20Expressions,%203rd%20Edition/0596528124/regex3-CHP-10-SECT-4.html
	if (! preg_match('{\\\\(?:/;$)}', $raw_regex)){ /* '/' followed by '\' or EOS */
		/* There are no already-escaped forward slashes, and no escape at the end, so it's
		 * safe to blindly escape forward slashes. */
		$cooked = preg_replace('!/!', '\/', $raw_regex);
	}else{
		/* This is the pattern we'll use to parse $raw_regex.
		 * The two parts whose matches we'll need to escape are within capturing parens. */
		$pattern = '{ [^\\\\/]+ |\\\\. |( / |\\\\$ ) }sx';
		/* Our callback function is called upon each successful match of $pattern in $raw-regex.
		 * If $matches[1] is not empty, we return an escaped version of it.
		 * Otherwise, we simply return what was matched unmodified. */
		$f = create_function('$matches', '            // This long
				if (empty($matches[1]))       // singlequoted
				return $matches[0];           // string becomes
				else                          // our function
				return "\\\\" . $matches[1];  // code.
			');
		/* Actually apply $pattern to $raw_regex, yielding $cooked */
		$cooked = preg_replace_callback($pattern, $f, $raw_regex);
	}
	$delimiters = array(
			'/',
			array('{', '}'),
			array('[', ']'),
			array('(', ')'),
			'@',
			'#',
			'`',
			'~',
			'%',
			'&',
			'\'',
			'"',
			NULL
		);
	foreach($delimiters as $delimiter){
		if($delimiter == NULL){
			$cooked = addcslashes($cooked, "\0\r\n\f\v\t");
			throw new Exception("No safe delimiter found for the regex $cooked - please file a bug report.");
		}
		if(is_string($delimiter))
			$start = $end = $delimiter;
		elseif(is_array($delimiter))
			list($start, $end) = $delimiter;
		else
			throw new Exception('$delimiter is of an unrecognized type (neither array nor string).');
		if(strpos($cooked, $end) === FALSE){
			break;
		}
	}
	/* $cooked is now safe to wrap -- do so, append the modifiers, and return */
	return "$start$cooked$end$modifiers";
}

class GrandMatrix{

	public $array;
	private $parser;
	private $delimiter;
	
	public function __construct($delimiter = ',', $parser = NULL){
		$this->array = array();
		$this->delimiter = $delimiter;
		if(!is_null($parser))
			$this->parser = $parser;
	}
	
	// Set a value to the location of an array
	public function set($loc, $value){
		eval("\$this->" . $this->loc($loc) . " = \$value;");
	}
	public function delete($loc){
		eval("unset(\$this->" . $this->loc($loc) . ");");
	}
	
	// Get the value of an array at the location
	public function get($loc, $matrix = TRUE){
		if(!$this->isset($loc))
			return NULL;
		$result = eval("return \$this->" . $this->loc($loc) . ";");
		if(is_array($result) && $matrix){
			$matrix = new GrandMatrix($this->delimiter);
			$matrix->array = $result;
			$result = $matrix;
		}
		return $result;
	}
	
	// Process a location string	
	public function loc($loc){
		if(is_string($loc))
			$loc = isset($this->parser) ? call_user_func($this->parser, $loc, $this->delimiter) : $this->parse($loc);
		if($loc === '')
			return '';
		return '[\'' . implode('\'][\'', $loc) . '\']';
	}
	
	// Count the array at the location
	public function count($loc){
		return eval("return count(\$this->" . $this->loc($loc) . ");");
		// Analogous to: return count($this->get($loc));
	}
	
	// Return if array is empty at location
	public function isEmpty($loc){
		return eval("return empty(\$this->" . $this->loc($loc) . ");");
		// Analogous to: $tmp=$this->get($loc); return empty($tmp);
	}

	// Return if array isset at location
	public function isSet($loc){
		return eval("return isset(\$this->" . $this->loc($loc) . ");");
	}
	
	// Merge multi-dimensional arrays and return lost (overwritten) values
	public function merge($loc, $array, $overwrite = TRUE){
		if($this->isSet($loc))
			$matrix = $this->get($loc)->array;
		else{
			$this->set($loc, $array);
			return NULL;
		}
		if($array instanceof GrandMatrix)
			$array = $array->array;
		foreach($array as $index => $value){
			if(!isset($matrix[$index]) || is_null($matrix[$index])){
				$this->set($loc . $this->delimiter . $index, $value);
				$array[$index] =& $matrix[$index];
				continue;
			}
			if(is_array($matrix[$index]) && is_array($value)){
				$array[$index] = $this->merge($loc . $this->delimiter . $index, $value, $overwrite);
				continue;
			}
			if($overwrite){
				$this->set($loc . $this->delimiter . $index, $value);
				$array[$index] = $matrix[$index];
			}
		}
		return $array;
	}
	
	public function iterate($loc, $callback){
		$array = $this->get($loc, FALSE);
		if(is_array($array))
			foreach($array as $index => $value){
				$loc_ = $loc . $this->delimiter . $index;
				$oldIndex = $index;
				$oldValue = $value;
				$result = $callback($index, $value);
				if($index !== $oldIndex){
					$this->delete($loc . $this->delimiter . $oldIndex);
					$this->set($loc . $this->delimiter . $index, $value);
				}elseif($value !== $oldValue){
					$this->set($loc . $this->delimiter . $index, $value);
				}
				if($result)
					$this->iterate($loc_, $callback);
			}
	}
	
	// Print out the array
	public function __toString(){
		return print_r($this->array, true);
	}
	
	private function parse($loc){
		if($loc === ''){
			return '';
			/* Should this mean $this->array (as now) or $this->array['']?
			return '\'\''; */
		}
		$token = "[^\\\\{$this->delimiter}]*";
		if(preg_match(preg_regex_to_pattern("^$token({$this->delimiter}$token)*$", 'i'), $loc))
			return 'array' . explode($this->delimiter, collapse($loc, $this->delimiter));
		else
			trigger_error("Invalid location path ($loc) for multi-dimentional array", E_USER_WARNING);
	}
}
?>
